/* The Computer Language Benchmarks Game
   https://salsa.debian.org/benchmarksgame-team/benchmarksgame/
   contributed by Ralph Ganszky
   Added dummy element to tuple (because I read an article that this helps
   to block less writing back to memory) and store data in
   UnsafedMutablePointer to reduce overhead of array access.
   
   modified by Patrick Sy
*/
import Glibc
class Timer {
    private let CLOCK_REALTIME = 0
    private var start_timespec = timespec()
    private var end_timespec = timespec()
    private var time_spec = timespec()
    func start() {
        clock_gettime(Int32(CLOCK_REALTIME),&start_timespec)
    }
    
    func stop() -> Double {
        clock_gettime(Int32(CLOCK_REALTIME),&end_timespec)
        let start_time = Double(start_timespec.tv_sec * 1_000_000 + start_timespec.tv_nsec / 1_000)
        let end_time = Double(end_timespec.tv_sec * 1_000_000 + end_timespec.tv_nsec / 1_000)
        let time = end_time - start_time
        return time / 1_000
    }
    func getTime() -> Double {
        clock_gettime(Int32(CLOCK_REALTIME),&time_spec)
        return Double(time_spec.tv_sec * 1_000_000 + time_spec.tv_nsec / 1_000)
    }
}
let PI = Double.pi
let SOLAR_MASS = 4 * PI * PI
let DAYS_PER_YEAR = 365.24

class Body {
    var x: Double
    var y: Double
    var z: Double
    var vx: Double
    var vy: Double
    var vz: Double
    var mass: Double
    
    init(x: Double, y: Double, z: Double, vx: Double, vy: Double, vz: Double, mass: Double) {
        self.x = x
        self.y = y
        self.z = z
        self.vx = vx
        self.vy = vy
        self.vz = vz
        self.mass = mass
    }
}
func jupiter() -> Body {
    return Body(
        x: 4.84143144246472090e+00,
        y: -1.16032004402742839e+00,
        z: -1.03622044471123109e-01,
        vx: 1.66007664274403694e-03 * DAYS_PER_YEAR,
        vy: 7.69901118419740425e-03 * DAYS_PER_YEAR,
        vz: -6.90460016972063023e-05 * DAYS_PER_YEAR,
        mass: 9.54791938424326609e-04 * SOLAR_MASS
    )
}

func saturn() -> Body {
    return Body(
        x: 8.34336671824457987e+00,
        y: 4.12479856412430479e+00,
        z: -4.03523417114321381e-01,
        vx: -2.76742510726862411e-03 * DAYS_PER_YEAR,
        vy: 4.99852801234917238e-03 * DAYS_PER_YEAR,
        vz: 2.30417297573763929e-05 * DAYS_PER_YEAR,
        mass: 2.85885980666130812e-04 * SOLAR_MASS
    )
}

func uranus() -> Body {
    return Body(
        x: 1.28943695621391310e+01,
        y: -1.51111514016986312e+01,
        z: -2.23307578892655734e-01,
        vx: 2.96460137564761618e-03 * DAYS_PER_YEAR,
        vy: 2.37847173959480950e-03 * DAYS_PER_YEAR,
        vz: -2.96589568540237556e-05 * DAYS_PER_YEAR,
        mass: 4.36624404335156298e-05 * SOLAR_MASS
    )
}

func neptune() -> Body {
    return Body(
        x: 1.53796971148509165e+01,
        y: -2.59193146099879641e+01,
        z: 1.79258772950371181e-01,
        vx: 2.68067772490389322e-03 * DAYS_PER_YEAR,
        vy: 1.62824170038242295e-03 * DAYS_PER_YEAR,
        vz: -9.51592254519715870e-05 * DAYS_PER_YEAR,
        mass: 5.15138902046611451e-05 * SOLAR_MASS
    )
}

func sun() -> Body {
    return Body(x: 0.0, y: 0.0, z: 0.0, vx: 0.0, vy: 0.0, vz: 0.0, mass: SOLAR_MASS)
}

var bodies: [Body] = [sun(), jupiter(), saturn(), uranus(), neptune()]

func advance(bodies: inout[Body], dt: Double) {
    let size = bodies.count
    
    for i in 0..<size {
        var bodyi = bodies[i]
        var vxi = bodyi.vx
        var vyi = bodyi.vy
        var vzi = bodyi.vz
        let xi = bodyi.x
        let yi = bodyi.y
        let zi = bodyi.z
        let massi = bodyi.mass
        
        for j in (i+1)..<size {
            var bodyj = bodies[j]
            let dx = xi - bodyj.x
            let dy = yi - bodyj.y
            let dz = zi - bodyj.z
            
            let d2 = dx * dx + dy * dy + dz * dz
            let mag = dt / (d2 * sqrt(d2))
            //let mag = dt / (d2 * d2)
            let massiMag = massi * mag
            
            let massj = bodyj.mass
            let massjMag = massj * mag
            vxi -= dx * massjMag
            vyi -= dy * massjMag
            vzi -= dz * massjMag
            
            bodyj.vx += dx * massiMag
            bodyj.vy += dy * massiMag
            bodyj.vz += dz * massiMag
            
            bodies[j] = bodyj
        }
        
        bodyi.vx = vxi
        bodyi.vy = vyi
        bodyi.vz = vzi
        
        bodyi.x += dt * vxi
        bodyi.y += dt * vyi
        bodyi.z += dt * vzi
        
        bodies[i] = bodyi
    }
}

func energy(bodies: [Body]) -> Double {
    var e = 0.0
    let size = bodies.count
    
    for i in 0..<size {
        let bodyi = bodies[i]
        e += 0.5 * bodyi.mass * (bodyi.vx * bodyi.vx + bodyi.vy * bodyi.vy + bodyi.vz * bodyi.vz)
        
        for j in (i+1)..<size {
            let bodyj = bodies[j]
            let dx = bodyi.x - bodyj.x
            let dy = bodyi.y - bodyj.y
            let dz = bodyi.z - bodyj.z
            
            let distance = sqrt(dx * dx + dy * dy + dz * dz)
            e -= (bodyi.mass * bodyj.mass) / distance
        }
    }
    
    return e
}

func runNBody() -> Int {
    let n = 1000000
    print(energy(bodies: bodies))
    let timer = Timer()
    timer.start()
    for _ in 0..<n {
        advance(bodies: &bodies, dt: 0.01)
    }
    let time = timer.stop()
    print("NBody - RunNBody: \t" + String(time) + "\tms" )
    return Int(time)
}
_ = runNBody()